/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.jaywalkable.vkeyboard;

import static com.jaywalkable.vkeyboard.BengaliLetter.KA;
import static com.jaywalkable.vkeyboard.BengaliLetter.SIGN_VIRAMA;

import java.security.InvalidParameterException;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.NavigableSet;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.jaywalkable.vkeyboard.impl.BengaliVKeyboardLayout;
import com.jaywalkable.vkeyboard.impl.SimpleVTextComponent;
import com.jaywalkable.vkeyboard.util.StringLengthComparator;

/**
 * Utility class for <code>VKeyboardLayout</code> implementations. This class instance is used to create
 * <code>VKeyboardLayoutEncoder</code> and <code>VKeyboardLayoutDecoder
 * </code> of <code>VKeyboardLayout</code> implementations.
 * 
 * Use <code>forName</code> method to create instance of this class. Parameter name can be any name of build-in
 * <code>VKeyboardLayout</code> implementations. You can use fully qualified <code>class</code> name of your own
 * implemntation of <code>VKeyboardLayout</code>. Use "BN_UNIJOY" to obtain instance of one such implementation of this
 * class for Bengali Unijoy keyboard available in this library.
 * 
 * @author Rubel Ahammad
 */
public final class VKeyboardEncoding {

    public static final String KEYBOARD_BN_SOMEWHEREIN_PHONETIC = "BN_SOMEWHEREIN_PHONETIC";
    public static final String KEYBOARD_BN_UNIJOY = "BN_UNIJOY";
    public static final String KEYBOARD_BN_AVRO_PHONETIC = "BN_AVRO_PHONETIC";
    private static final Map<String, VKeyboardEncoding> encodings = new HashMap<String, VKeyboardEncoding>();
    private final VKeyboardLayout keyboardLayout;
    /** TreeMap used to sort elements based on String length of its keys. */
    private final TreeMap<String, Character[]> encodingMap = new TreeMap<String, Character[]>(
            new StringLengthComparator());
    private final TreeMap<String, Character[]> decodingMap = new TreeMap<String, Character[]>(
            new StringLengthComparator());

    private VKeyboardEncoding(VKeyboardLayout keyboardLayout) {
        this.keyboardLayout = keyboardLayout;

        Map<Character[], Character[]> layout = this.keyboardLayout.getLayout();
        for (Character[] c : layout.keySet()) {
            Character[] chars = layout.get(c);
            if (c.length > 0) {
                decodingMap.put(createString(c), chars);
            }
            if (chars.length > 0) {
                String str = createString(chars);
                Character[] addedChars = encodingMap.get(str);
                if (addedChars != null) {
                    if (addedChars.length > chars.length) {
                        encodingMap.remove(str);
                        encodingMap.put(str, c);
                    }
                } else {
                    encodingMap.put(str, c);
                }
                
            }
        }

        // if (keyboardLayout instanceof VKeyboardDictionarySupport) {
        // VKeyboardDictionarySupport dictionarySupport = (VKeyboardDictionarySupport) keyboardLayout;
        // Hashtable<String, String> ht = dictionarySupport.loadDictionary();
        // VKeyboardKeyConverterImpl decodingConv = new VKeyboardKeyConverterImpl(decodingMap);
        // VKeyboard keyboard = new VKeyboardImpl();
        // keyboard.setConverter(decodingConv);
        //            
        // keyboard.setKeyboardLayout(keyboardLayout);
        //            
        // for (String key: ht.keySet()) {
        // VTextComponent vTextComponent = new SimpleVTextComponent();
        // keyboard.setVTextComponent(vTextComponent );
        // String value = ht.get(key);
        // Character[] valChars = new Character[value.length()
        // ];
        // for (int i=0; i< valChars.length; i++) {
        // valChars[i] = value.charAt(i);
        // keyboard.keyPressed(valChars[i]);
        // }
        // Character[] keyChars = new Character[vTextComponent.getText().length()];
        // for (int i=0; i< keyChars.length; i++) {
        // keyChars[i] = vTextComponent.getText().charAt(i);
        // }
        //                
        // // Character[] convert = decodingConv.convert(value);
        // decodingMap.put(key, keyChars);
        //                
        // encodingMap.put(vTextComponent.getText(), valChars);
        // }

        // }
    }

    /**
     * Returns the instance of <code>VKeyboardEncoding</code> associated with <code>VKeyboardLayout</code>
     * implementation.
     * 
     * @param name
     *            the name of encoding scheme.
     * @return VKeyboardEncoding the instance of <code>VKeyboardEncoding</code> associated with
     *         <code>VKeyboardLayout</code> implementation.
     */
    public static VKeyboardEncoding forName(String name) {
        VKeyboardLayout keyboardLayout = null;
        VKeyboardEncoding enc = encodings.get(name);
        if (enc != null) {
            return enc;
        }
        if (KEYBOARD_BN_UNIJOY.equalsIgnoreCase(name)) {
            keyboardLayout = new UnijoyVKeyboardLayout();
        } else if (KEYBOARD_BN_SOMEWHEREIN_PHONETIC.equalsIgnoreCase(name)) {
            keyboardLayout = new SomewhereinPhoneticVKeyboardLayout();
        } else if (KEYBOARD_BN_AVRO_PHONETIC.equalsIgnoreCase(name)) {
            keyboardLayout = new AvroPhoneticVKeyboardLayout();
        } else {
            try {
                keyboardLayout = (VKeyboardLayout) Class.forName(name).newInstance();
            } catch (Exception ex) {
                Logger.getLogger(VKeyboardEncoding.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        if (keyboardLayout == null) {
            throw new InvalidParameterException("Invalid name provided.");
        } else {
            enc = new VKeyboardEncoding(keyboardLayout);
            encodings.put(name, enc);
        }
        return enc;
    }

    /**
     * Returns the <code>VKeyboardLayoutEncoder</code> associated with <code>VKeyboardLayout</code> implementation.
     * 
     * @return VKeyboardLayoutEncoder the encoder associated with <code>VKeyboardLayout</code> implementation.
     */
    public VKeyboardEncoder newEncoder() {
        return this.new Encoder();
    }

    /**
     * Returns the <code>VKeyboardLayoutDecoder</code> associated with <code>VKeyboardLayout</code> implementation.
     * 
     * @return VKeyboardLayoutDecoder the decoder associated with <code>VKeyboardLayout</code> implementation.
     */
    public VKeyboardDecoder newDecoder() {
        return this.new Decoder();
    }

    private class Encoder extends VKeyboardKeyConverterImpl implements VKeyboardEncoder {
        private final StringBuilder sb = new StringBuilder();
        private int replaceCount;
        private Character[] lastEncodedChars = null;

        /**
         * Populates the <code>TreeMap</code> with keyboard layout values.
         */
        private Encoder() {
            super(encodingMap);
        }

        public String applyEncodingRules(String str) {
            String encoded = str;
            NavigableSet<String> descendingKeySet = encodingMap.descendingKeySet();
            VKeyboardKeyConverter en2bnConverter = new VKeyboardKeyConverterImpl(decodingMap);
            if (keyboardLayout instanceof BengaliVKeyboardLayout) {
                BengaliVKeyboardLayout bnKeyboarLayout = (BengaliVKeyboardLayout) keyboardLayout;
                if (bnKeyboarLayout.getSeparatorChar() != null) {
                    for (String key : descendingKeySet) {
                        // Character[] chars = encodingMap.get(key);
                        boolean isEligible = false;
                        char[] chars = key.toCharArray();
                        if ((chars.length == 3) && chars[1] == SIGN_VIRAMA) {
                            Character[] engChars0 = convert(String.valueOf(chars[0]));
                            if (engChars0.length != 1) {
                                continue;
                            }
                            Character[] engChars2 = convert(String.valueOf(chars[2]));
                            if (engChars2.length != 1) {
                                continue;
                            }

                            String enChars = new String(new char[] { engChars0[0], engChars2[0] });

                            Character[] repChars = en2bnConverter.convert(enChars);
                            if (repChars != null && repChars.length > 0) {
                                String regex = new String(new char[] { chars[0], chars[2] });
                                String replacement = createString(engChars0) + bnKeyboarLayout.getSeparatorChar()
                                        + createString(engChars2);
                                encoded = encoded.replaceAll(regex, replacement);
                            }

                        }
                        // if (chars.length == 5 && chars[1].equals(SIGN_VIRAMA) && chars[3].equals(SIGN_VIRAMA)) {
                        //
                        // }

                    }
                }
            }
            
            for (String key : descendingKeySet) {
                Character[] chars = encodingMap.get(key);
                String createString = createString(chars);
                encoded = encoded.replaceAll(key, createString);
            }
            
            return encoded;
        }

        private void reset() {
            sb.setLength(0);
            replaceCount = 0;
        }

        // @Override
        // public void encode(Character ch) {
        // this.encode(vTextComponent, ch);
        // }

        private void encode(VTextComponent vTextComponent, Character ch) {
            String lastTyped = sb.toString();
            String str = sb.append(ch).toString();

            Character[] decoded = super.convert(str);

            ifcond: if (decoded == null) {
                if (foundOnePrefixMatch()) {
                    return;
                }
                reset();
                str = String.valueOf(ch);
                sb.append(str);
                decoded = super.convert(str);
                if (decoded == null) {
                    if (!foundOnePrefixMatch()) {
                        reset();
                    }
                    return;
                } else {
                    // if (keyboardLayout instanceof BengaliVKeyboardLayout) {
                    // BengaliVKeyboardLayout bengaliLayout = (BengaliVKeyboardLayout) keyboardLayout;
                    //
                    // if (bengaliLayout.getSeparatorChar() != null && lastTyped != null) {
                    // if (!bengaliLayout.isNgaCategory(decoded[0]) && !bengaliLayout.isVowel(decoded[0])
                    // && !bengaliLayout.isVowel(lastTyped.charAt(lastTyped.length() - 1))) {
                    // Character[] tmpDecoded = decoded;
                    // decoded = new Character[tmpDecoded.length + 1];
                    // System.arraycopy(tmpDecoded, 0, decoded, 1, tmpDecoded.length);
                    // decoded[0] = bengaliLayout.getSeparatorChar();
                    // }
                    // }
                    // }
                    break ifcond;
                }

            }

            vTextComponent.keyTyped(replaceCount, decoded);
            boolean hasMultpleRules = super.foundMultiplePrefixMatch();
            if (hasMultpleRules) {
                replaceCount = decoded.length;
            } else if (foundExactMatchOnly()) {
                reset();
            }
        }
    }

    private class Decoder extends VKeyboardKeyConverterImpl implements VKeyboardDecoder {
        private VKeyboardKeyConverterImpl converter = null;

        private Decoder() {
            super(decodingMap);
            converter = new VKeyboardKeyConverterImpl(decodingMap);
        }

        public Character[] convert(Character cin) {
            Character[] decoded = convert(String.valueOf(cin));
            return decoded;
        }

        public String applyDecodingRules(String str) {
            return super.applyDecodingRules(str);
        }
    }

    private String createString(Character[] chars) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < chars.length; i++) {
            sb.append(String.valueOf(chars[i]));
        }
        return sb.toString();
    }

    public VKeyboardLayout getVKeyboardLayout() {
        return keyboardLayout;
    }

    public VKeyboard getVKeyboard() {
        VKeyboard keyboard = new VKeyboardImpl(new SimpleVTextComponent());
        keyboard.setKeyboardLayout(keyboardLayout);
        keyboard.setConverter(new VKeyboardKeyConverterImpl(decodingMap));
        // if (keyboardLayout instanceof VKeyboardDictionarySupport) {
        // keyboard.setDictionary(((VKeyboardDictionarySupport)keyboardLayout).loadDictionary());
        // }
        return keyboard;
    }
}
